<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <title>画板</title>
    <style>
        body,
        html {
            width: 100%;
            height: 100%;
            background-repeat: no-repeat;
            background-position: 0 0 no-repeat;
            background-size: cover;
            overflow: hidden;
            font-family: "Microsoft yahei";
            background-color: #fff;
        }

        li {
            list-style-type: none;
        }

        ul {
            padding-left: 10px
        }

        h1 {
            position: fixed;
            left: 15px;
            top: 5px;
            width: 250px;
            height: 60px;
            color: #ff5e83;
            font-size: 26px;
            line-height: 60px;
            text-align: center;
        }

        canvas {
            position: fixed;
            left: 280px;
            top: 5px;
            display: block;
            cursor: pointer;
            background-color: #FFFFFF;
            border: 1px solid #CCCCCC;
        }

        .container {
            width: 250px;
            height: 60px;
            position: fixed;
            left: 15px;
            top: 70px;
        }

        .container h2 {
            width: 100%;
            height: 60px;
            font-size: 22px;
            text-align: center;
            color: #ff377e;
            font-weight: normal;
            line-height: 60px;
            border-bottom: 1px solid #000;
        }

        .container h3 {
            width: 100%;
            height: 30px;
            font-size: 18px;
            text-align: center;
            color: #41b1ff;
            font-weight: normal;
            line-height: 30px;
        }

        .row {
            width: 100%;
            height: auto;
            padding-bottom: 100px;
            background-color: rgba(192, 192, 192, 0.50);
            overflow: hidden;
        }

        .type {
            width: 100%;
            height: auto;
            margin: 20px 0;
            padding-bottom: 10px;
            border-bottom: 1px solid #fff;
        }

        .type:after {
            content: "";
            display: block;
            clear: both;
        }

        .type li {
            float: left;
            width: 90px;
            height: 45px;
            margin: 0 auto;
            background-color: #0078ff;
            color: #fff;
            font-size: 18px;
            line-height: 45px;
            text-align: center;
            margin-bottom: 15px;
            border-radius: 5px;
            cursor: pointer;
            transition: all 0.7s;
        }

        .type li:hover {
            background-color: #fff;
            color: #0078ff;
            box-shadow: 0 0 10px rgba(0, 120, 255, 0.77);
        }

        .type .typeactive {
            background-color: #fff;
            color: #0078ff;
            box-shadow: 0 0 10px rgba(0, 120, 255, 0.77);
        }

        .type li:nth-child(odd) {
            margin-left: 25px;
        }

        .type li:nth-child(even) {
            margin-left: 20px;
        }

        .style {
            width: 100%;
            height: 40px;
        }

        .style li {
            float: left;
            width: 90px;
            height: 45px;
            background-color: #FFBC00;
            color: #fff;
            font-size: 18px;
            line-height: 45px;
            text-align: center;
            border-radius: 5px;
            cursor: pointer;
            transition: all 0.7s;
        }

        .style li:hover {
            color: #ffa800;
            background-color: #fff;
            box-shadow: 0 0 10px rgba(255, 232, 95, 0.77);
        }

        .style .styleactive {
            color: #ffa800;
            background-color: #fff;
            box-shadow: 0 0 10px rgba(255, 232, 95, 0.77);
        }

        .style li:nth-child(1) {
            margin-left: 25px;
        }

        .style li:nth-child(2) {
            margin-left: 20px;
        }

        .poly {
            position: relative;
        }

        .bian {
            position: absolute;
            left: 100px;
            top: 0;
            width: 90px;
            height: auto;
            border-radius: 5px;
            background-color: #EC494E;
            overflow: hidden;
            overflow: hidden;
            transform: scale(1.2);
            display: none;
        }

        .bian span {
            display: block;
            float: left;
            width: 40px;
            height: 45px;
            font-size: 16px;
            color: #fff;
            text-align: center;
            line-height: 40px;
            color: #fff;
            transition: all 0.7s;
        }

        .bian input {
            float: left;
            width: 45px;
            height: 35px;
            margin-top: 5px;
            border: 1px solid #fff;
            box-sizing: border-box;
            text-align: center;
            line-height: 40px;
            font-size: 18px;
            color: #fff;
            background: #EC494E;
            border-radius: 3px;
            box-shadow: 0 0 0 4px #fff inset;
            transition: all 0.7s;
        }

        .cut,
        .create,
        .back {
            float: left;
            width: 90px;
            height: 45px;
            margin-left: 25px;
            margin-bottom: 15px;
            background-color: #5bd219;
            color: #fff;
            font-size: 18px;
            line-height: 45px;
            text-align: center;
            border-radius: 5px;
            cursor: pointer;
        }

        .copy,
        .clear,
        .save {
            float: left;
            width: 90px;
            height: 45px;
            margin-left: 20px;
            margin-bottom: 15px;
            background-color: #5bd219;
            color: #fff;
            font-size: 18px;
            line-height: 45px;
            text-align: center;
            border-radius: 5px;
            cursor: pointer;
        }

        .shezhi {
            transition: all 0.7s;
        }

        .shezhistyle {
            background: #fff;
            color: #5bd219;
        }

        .create {
            position: relative;
        }

        .xinjian {
            position: absolute;
            left: 0;
            top: 0;
            width: 200px;
            padding: 10px 0;
            border-radius: 5px;
            background-color: #EC494E;
            box-shadow: 0 0 15px rgba(236, 73, 78, 0.76);
            cursor: default;
            display: none;
        }

        .xinjian:hover {
            color: #fff;
        }

        .xinjian_before {
            position: absolute;
            right: 0;
            top: 0;
            width: 20px;
            height: 20px;
            color: #fff;
            line-height: 16px;
            text-indent: -1px;
            font-size: 30px;
            border: 1px solid #fff;
            transform: rotate(45deg);
            border-radius: 50%;
            cursor: pointer;
        }

        .xinjian h6 {
            width: 100%;
            height: 50px;
            color: #fff;
            line-height: 50px;
            text-align: center;
            font-size: 20px;
            font-weight: normal;
        }

        #ding {
            width: 60px;
            height: 30px;
            font-size: 14px;
            border-radius: 3px;
            color: #fff;
            background-color: #0078ff;
            border: 0;
            outline: none;
            cursor: pointer;
            box-shadow: 0 0 10px rgba(0, 120, 255, 0.5);
        }

        .xinjian_width,
        .xinjian_height {
            float: left;
            width: 85px;
            height: 40px;
            margin-left: 10px;
            border-radius: 5px;
            margin-bottom: 20px;
            background-color: #FFBC00;
        }

        .xinjian_width:hover,
        .xinjian_height:hover {
            background-color: #fff;
            color: #000;
        }

        .xinjian_width span,
        .xinjian_height span {
            float: left;
            width: 30px;
            height: 100%;
            font-size: 16px;
            text-align: center;
            line-height: 40px;
        }

        .xinjian_width input,
        .xinjian_height input {
            float: left;
            margin-top: 5px;
            width: 50px;
            height: 30px;
            text-align: center;
            line-height: 30px;
            font-size: 14px;
            border: 1px solid #FFBC00;
            box-sizing: border-box;
        }

        .box {
            float: left;
            width: 90px;
            height: auto;
            background-color: #EC494E;
            overflow: hidden;
            border-radius: 5px;
            transition: all 0.7s;
        }

        .box {
            margin-left: 25px;
        }

        .linewidth {
            margin-left: 20px;
        }

        .box:hover {
            background-color: #fff;
        }

        .box span {
            display: block;
            float: left;
            width: 40px;
            height: 45px;
            font-size: 16px;
            color: #fff;
            text-align: center;
            line-height: 40px;
            color: #fff;
            transition: all 0.3s;
        }

        .box input {
            float: left;
            width: 45px;
            height: 35px;
            margin-top: 5px;
            border: 1px solid #fff;
            box-sizing: border-box;
            text-align: center;
            line-height: 40px;
            font-size: 18px;
            color: #fff;
            background: #EC494E;
            border-radius: 3px;
            box-shadow: 0 0 0 4px #fff inset;
            transition: all 0.3s;
        }

        .box2 span {
            width: 55px;
        }

        .box2 input {
            width: 140px;
        }

        .space {
            float: left;
            width: 100%;
            height: 0;
            border-bottom: 1px solid #fff;
            margin: 20px 0;
        }
    </style>
</head>

<body>
    <h1>画板</h1>
    <div class="container">
        <div class="row">
            <ul name="" class="type">
                <li class="line typeactive" data="line">直&nbsp;&nbsp;&nbsp;线</li>
                <li class="rect" data="rect">矩&nbsp;&nbsp;&nbsp;形</li>
                <li class="circle" data="circle">圆&nbsp;&nbsp;&nbsp;圈</li>
                <li class="pen" data="pen">铅&nbsp;&nbsp;&nbsp;笔</li>
                <li class="poly" data="poly">多&nbsp;边&nbsp;形
                    <div class="bian"><span>边数</span><input type="number" name="number" min="3" value="3" max="15">
                    </div>
                </li>
                <li class="eraser" data="eraser">橡&nbsp;&nbsp;&nbsp;皮</li>
            </ul>
            <ul class="style">
                <li class="stroke styleactive">描&nbsp;&nbsp;&nbsp;边</li>
                <li class="fill">填&nbsp;&nbsp;&nbsp;充</li>
            </ul>
            <div class="space"></div>
            <div class="box"><span>颜色</span></label><input type="color" name="color" value="#000000"></div>
            <div class="box linewidth"><span>线宽</span><input type="number" name="number" value="1" max="150" min="1">
            </div>
            <div class="space"></div>
            <div class="create shezhi">新&nbsp;&nbsp;&nbsp;建
                <div class="xinjian">
                    <h6>画 板 尺 寸</h6>
                    <div class="xinjian_width">
                        <span>宽</span>
                        <input type="number" max="1070" min="500" value="500">
                    </div>
                    <div class="xinjian_height">
                        <span>高</span>
                        <input type="number" max="648" min="300" value="300">
                    </div>
                    <input type="button" name="queding" value="确定" id="ding">
                    <div class="xinjian_before">+</div>
                </div>
            </div>
            <div class="clear shezhi">清&nbsp;&nbsp;&nbsp;空</div>
            <div class="back shezhi">撤&nbsp;&nbsp;&nbsp;销</div>
            <div class="save shezhi">保&nbsp;&nbsp;&nbsp;存</div>
            <div class="cut shezhi" data="cut">剪&nbsp;&nbsp;&nbsp;切</div>
            <div class="copy shezhi">复&nbsp;&nbsp;&nbsp;制</div>
        </div>
    </div>
    <canvas id="canvas" width="1070" height="648"></canvas>
</body>

<script src="./jquery.min.js"></script>
<script>
    //定义画板2d工作区
    var canvas = document.getElementById("canvas")
    var ctx = canvas.getContext("2d")

    //设置默认样式
    $(".fill").css({ display: "none" })

    //声明变量
    var screenWidth = document.documentElement.clientWidth;
    var screenHeight = document.documentElement.clientHeight;
    var width = screenWidth - 295;
    var height = screenHeight - 15;
    canvas.width = width;
    canvas.height = height;
    var arr = [];

    //存储开始绘画起点
    var startPoint = {}
    //存储绘画结束
    var restorePoint = {}
    var isDraw = false //false 说明没在绘画 true绘画中

    //设置初始化
    var type = "line"
    //声明样式
    var style = "stroke"
    var color = "#000"
    var linewidth = "1"
    //声明起始点、结束点坐标
    var x = y = 0
    var w = h = 0



    //多边形
    var poly = $(".poly");
    //多边形边数（html显示部分）
    var bian = $(".bian")
    //多边形变数输入框
    var polyValue = $(".bian input");
    //多边形边数（默认值为3）
    var num = 3
    // 多边形
    poly.hover(function () {
        //fadeIn() 方法使用淡入效果来显示被选元素,假如该元素是隐藏的
        bian.fadeIn();
    }, function () { bian.fadeOut() })
    //多边形边数
    polyValue.change(function () { num = this.value })


    //获取颜色、线宽
    var colorValue = document.querySelector("input[type=color]");
    var widthValue = document.querySelector(".linewidth input[type=number]");
    // 颜色选择
    colorValue.onchange = function () { color = this.value; }
    // 粗细改变
    widthValue.onchange = function () { linewidth = this.value; }

    //获取直线、矩形、圆圈...DOM元素
    var typeItem = $(".type li")
    //给直线、矩形、圆圈..绑定样式修改
    typeItem.each(function () {
        $(this).click(function () {
            //初始化li的所有样式
            typeItem.removeClass("typeactive")
            //给当前点击的标签typeactive class名称
            $(this).toggleClass("typeactive")

            //将点击的标签上data属性赋值给type，判断当前点击的是哪一个标签
            type = $(this).attr("data")
            //当点击直线、铅笔时，隐藏填充按钮（默认隐藏）
            if (type == "line" || type == "pen") {
                style = "stroke";
                $(".stroke").addClass("styleactive");
                $(".fill").css({ display: "none" }).removeClass("styleactive");
            } else {
                $(".fill").css({ display: "block" })
            }
        })
    })


    //描边填充标签
    var StrokeFill = $(".style li");
    //描边、填充单击事件(修改样式)
    StrokeFill.each(function (index, ele) {
        $(ele).click(function () {
            style = $(this).attr("class");
            StrokeFill.removeClass("styleactive");
            $(this).toggleClass("styleactive");
        })
    })

    /// 新建画布
    //获取新建画布DOM元素
    var create = $(".create");
    var xinjian = $(".xinjian");
    var create_close = $(".xinjian_before");
    //获取输入的宽高值
    var canvasWidth = document.querySelector(".xinjian_width input[type=number]");
    var canvasHeight = document.querySelector(".xinjian_height input[type=number]");
    canvasWidth.value = width; canvasHeight.value = height;
    canvasWidth.max = screenWidth - 295; canvasHeight.max = screenHeight - 15;
    //确认按钮
    var ding = document.querySelector("#ding");

    create.click(function () { xinjian.fadeIn(); })
    create_close.click(function (e) {
        e.stopPropagation()
        xinjian.fadeOut();
    })
    //点击确定，将新的画布宽高赋值，清除原本的画布
    canvasWidth.onblur = function () {
        if (this.value <= this.min) {
            this.value = this.min;
        }
        if (this.value >= screenWidth - 295) {
            this.value = screenWidth - 295;
        }
        width = this.value;
    }
    canvasHeight.onblur = function () {
        if (this.value <= this.min) {
            this.value = this.min;
        }
        if (this.value >= screenHeight - 15) {
            this.value = screenHeight - 15;
        }
        height = this.value;
    }
    //确定按钮点击事件
    ding.onclick = function (e) {
        canvas.width = width;
        canvas.height = height;
        canvas.style.left = (screenWidth + 295 - canvas.width) / 2 + "px";
        canvas.style.top = (screenHeight - 5 - canvas.height) / 2 + "px";
        arr = [];
        ctx.clearRect(0, 0, width, height);
        e.stopPropagation()
        xinjian.fadeOut();
    }

    // 清除
    var clear = $(".clear");
    clear.click(function () {
        arr = [];
        ctx.clearRect(0, 0, width, height);
    })

    // 撤销
    var back = $(".back");
    back.click(function () {
        arr.pop();
        ctx.clearRect(0, 0, width, height);
        if (arr.length > 0) {
            ctx.putImageData(arr[arr.length - 1], 0, 0, 0, 0, width, height);
        }
    })

    // 保存(模拟点击a标签)
    var save = $(".save");
    save.click(function () {
        var reg = canvas.toDataURL("image/png");//跳转页面手动保存
        //var reg=canvas.toDataURL("image/png").replace("image/png","image/octet-stream");//直接自动保存下载
        //创建a元素
        var a = document.createElement('a')
        a.href = reg
        a.download = 'download.png'
        //触发模拟点击
        a.click()
        //最后下载下来了那么download和a都没有用了就销毁
        a.remove()
    })

    //裁剪、复制
    var cut = $(".cut");
    var copy = $(".copy");
    //裁剪、复制状态控制变量
    var cutflag = false;
    var iscut = true;
    //相当于剪切板（存放中间数据）
    var cutdata;
    var lx, ly, lw, lh;
    //裁剪点击事件（样式修改）
    cut.click(function () {
        type = $(this).attr("data");
        typeItem.removeClass("typeactive");
        //$(this).css({ color: "#5bd219", backgroundColor: "#fff" }).toggleClass("shezhistyle");
        $(this).toggleClass("shezhistyle");
        copy.removeClass("shezhistyle")
        iscut = true;
    })
    //复制点击事件（样式修改）
    copy.click(function () {
        type = "cut";
        typeItem.removeClass("typeactive");
        $(this).toggleClass("shezhistyle");
        cut.removeClass("shezhistyle")
        iscut = false;
    })



    //构造一个原型对象（属性集合，方法集合）这样统一管理，需要使用哪一种方法时直接调用就行
    function Draw(ctx, setting) {
        //属性集合
        this.obj = ctx;
        this.type = setting.type || "stroke"
        this.color = setting.color || "#000"
        this.width = setting.width || "1"
    };
    // 方法集合
    Draw.prototype = {
        //样式统一调配
        init: function () {
            this.obj.strokeStyle = this.color
            this.obj.fillStyle = this.color
            this.obj.lineWidth = this.width
        },
        //直线
        line: function (x, y, x1, y1) {
            this.init()
            this.obj.beginPath()
            this.obj.moveTo(x, y)
            this.obj.lineTo(x1, y1)
            this.obj.stroke()
        },
        //矩形
        rect: function (x, y, x1, y1) {
            this.init();
            this.obj.beginPath();
            this.obj.rect(x, y, x1 - x, y1 - y);
            if (this.type == "stroke") {
                this.obj.stroke();
            } else if (this.type == "fill") {
                this.obj.fill();
            }
        },
        //圆圈
        circle: function (x, y, x1, y1) {
            this.init();
            var r = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));
            this.obj.beginPath();
            this.obj.arc(x, y, r, 0, 2 * Math.PI);
            if (this.type == "stroke") {
                this.obj.stroke();
            } else if (this.type == "fill") {
                this.obj.fill();
            }
        },
        //铅笔
        pen: function (x, y, x1, y1) {
            this.init();
            this.obj.save();
            this.obj.lineCap = "round";
            this.obj.lineTo(x1, y1);
            this.obj.stroke();
            this.obj.restore();
        },
        //多边形
        poly: function (x, y, x1, y1, n) {
            this.init();
            var obj = this.obj;
            var r = Math.sqrt(Math.pow(x - x1, 2) + Math.pow(y - y1, 2));;
            obj.save();
            obj.translate(x, y);
            obj.rotate(Math.PI / 2);
            var nx = r * Math.cos(Math.PI / n);
            var ny = r * Math.sin(Math.PI / n);
            obj.beginPath();
            obj.lineCap = "round";
            obj.moveTo(nx, ny);
            for (var i = 0; i <= n; i++) {
                obj.rotate(Math.PI * 2 / n);
                obj.lineTo(nx, -ny);
            }
            if (this.type == "stroke") {
                this.obj.stroke();
            } else if (this.type == "fill") {
                this.obj.fill();
            }
            obj.restore();
        },
        //橡皮
        eraser: function (x, y, x1, y1) {
            this.obj.lineCap = "round";
            this.obj.clearRect(x1 - 5, y1 - 5, 10, 10);
        },
        //裁剪
        cut: function (x, y, x1, y1) {
            this.init();
            this.obj.save();
            this.obj.setLineDash([4, 2]);
            this.obj.beginPath();
            this.obj.lineWidth = 1;
            this.obj.rect(x, y, x1 - x, y1 - y);
            this.obj.stroke();
            this.obj.restore();
        }
    }

    //鼠标按下事件 (putImageData()、getImageData()结合使用，保证绘画可以断断续续)
    canvas.onmousedown = function (e) {
        x = e.offsetX
        y = e.offsetY
        //修改绘画状态
        isDraw = true
        //getImageData()作用:用来获取canvas画布上指定矩形区域的像素数据;
        ctx.getImageData(0, 0, canvas.width, canvas.height);
        //铅笔
        if (type == "pen") {
            ctx.beginPath();
            ctx.moveTo(x, y);
        }
        //橡皮
        if (type == "eraser") {
            ctx.clearRect(x - 5, y - 5, 10, 10);
        }
        //裁剪
        if (cutflag && type == "cut") {
            if (arr.length != 0) {
                arr.splice(-1, 1);
            }
        }
        //实例化
        var draw = new Draw(ctx, { type: style, color: color, width: linewidth })
        //鼠标移动事件
        canvas.onmousemove = function (e) {
            w = e.offsetX
            h = e.offsetY
            //判断当前是否开始绘画
            if (isDraw) {
                //判断当前绘画类型
                if (type != "eraser") {
                    ctx.clearRect(0, 0, width, height);
                    if (arr.length != 0) {
                        //putImageData() 方法将图像数据(从指定的 ImageData 对象)放回画布上
                        ctx.putImageData(arr[arr.length - 1], 0, 0, 0, 0, width, height);
                    }
                }
                //当点击裁剪、裁剪状态为true 时执行
                if (cutflag && type == "cut") {
                    if (iscut) {
                        ctx.clearRect(lx, ly, lw - lx, lh - ly);
                    }
                    //定义新坐标（相当于你裁剪、复制完成后移动到哪里的坐标）
                    var nx = lx + (w - x);
                    var ny = ly + (h - y);
                    ctx.putImageData(cutdata, nx, ny);
                    //多边形
                } else if (type == "poly") {
                    draw[type](x, y, w, h, num);
                } else {
                    //其他类型  
                    //下面代码相当于  draw.类型的方法（x,y,w,h） 使用type变量替换具体名字，减少代码量
                    //不然直线、矩形、圆圈等等，每一个都要写一次，太麻烦
                    draw[type](x, y, w, h);
                }
            }
        }
        //鼠标弹起事件
        canvas.onmouseup = function () {
            //结束事件
            canvas.onmousemove = null;
            canvas.onmouseup = null;
            //结束绘画标识
            isDraw = false
            //裁剪
            if (type == "cut") {
                if (!cutflag) {
                    //修改裁剪状态
                    cutflag = true;
                    //获取修改后的状态
                    cutdata = ctx.getImageData(x + 1, y + 1, w - x - 2, h - y - 2);
                    lx = x; ly = y; lw = w; lh = h;
                } else {
                    cutflag = false;
                }
            }
            //将修改后的状态保存到中间变量arr中
            arr.push(ctx.getImageData(0, 0, width, height));
        }
    }
</script>

</html>